#ifndef VerTEX_ARRAY_H
#define VerTEX_ARRAY_H


#if defined(__APPLE__) || defined(MACOSX)
	   #include <OpenGL/OpenGL.h>
#endif
#if defined(_WIN32)
	#include <GL/gl.h>
#endif

#include <boost/multi_array.hpp>

#define BUFFER_OFFSET(bytes) ( (GLubyte*) NULL + (bytes) )

namespace Y{

template < class C >
class VertexContainer{
	
	public:
		GLenum format;
		
	public:
		VertexContainer():
			format( GL_FALSE )
		{}
		
		VertexContainer( GLenum f ):
			format(f)
		{}
		
		virtual ~VertexContainer () { }
		
		
		virtual int size() const = 0;
		virtual void interleavedArrays ( ) const = 0;
		virtual void interleavedArrays (GLenum f) const = 0;
		virtual void bindArray () const = 0;
		virtual void assignData () const = 0;
		virtual bool acceptableCoordinates (int x, int y = 1, int z = 1) const = 0;
		virtual void draw ( GLenum primtype, int start = 0, int length = -1 ) const = 0;
		
		virtual C& acc (int x, int y = 1, int z = 1) = 0;
		virtual C& operator () (int x, int y = 1, int z = 1) = 0;
		
		static void unbindBuffer () { glBindBuffer( GL_ARRAY_BUFFER , 0 ); }
	
};

template < class C >
class VertexArray{
	
	public:
		boost::multi_array<C,3> data;
		C * mappedData;
		GLenum format;
		int sizeX;
		int sizeY;
		int sizeZ;
		
		GLuint name;
		
	public:
	
		VertexArray(int sx = 1, int sy = 1, int sz = 1):
			data		( boost::extents[sx][sy][sz] ),
			mappedData	( NULL ),
			format		( C::format ),
			sizeX		( sx ),
			sizeY		( sy ),
			sizeZ		( sz ),
			name		( 0 )
		{ 
		}
		
		 ~VertexArray () {
			unsolidify();
		 }
		
		 void solidify (GLenum usage = GL_DYNAMIC_COPY) { 
			if ( name == 0 ){
				glGenBuffers(1,&name);
			}

				bindBuffer  ();
				
				glBufferData(GL_ARRAY_BUFFER, size() * sizeof(C), data.data(), usage);
	//				if (glGetError() == GL_OUT_OF_MEMORY){std::cout << "OpenGL Error: GL_OUT_OF_MEMORY while allocating VBO.";return;}
//			}
//			else{
//				bindBuffer ();
//				
//				glBufferSubData(GL_ARRAY_BUFFER,BUFFER_OFFSET(0),size() * sizeof(C), data.data());
//			}		
		}
		
		void unsolidify (){
			if ( name != 0 ){
				glDeleteBuffers(1,&name);
				name = 0;
			}
		}
		
		void map ( GLenum access = GL_READ_WRITE ){
			mappedData = (C *) glMapBuffer(GL_ARRAY_BUFFER,access);
		}
		
		void unmap ( ){
			glUnmapBuffer(GL_ARRAY_BUFFER);
			mappedData = NULL;
		}
		
		bool mapped (){ return (mappedData != NULL); }
		bool isMapped (){ return (mappedData != NULL); }
		
		 int size() const { return (sizeX * sizeY * sizeZ); }
		 void resize (int sx, int sy = 1, int sz = 1){
			data.resize( boost::extents[sx][sy][sz] );
			sizeX = sx;
			sizeY = sy;
			sizeZ = sz;
		 }
		
		 void interleavedArrays ( int byte_off = 0 ) const {
			if ( format == GL_FALSE ){ std::cout << "\n" << "must specify format for vertex array";return; }
			
			if ( name == 0 ){
				glInterleavedArrays( format,0,data.data() );
			}
			else{
				glInterleavedArrays( format,0,BUFFER_OFFSET(0) );
			}
		}
		 void interleavedArrays (GLenum f, int byte_off = 0 ) const {
			if ( name == 0 ){
				glInterleavedArrays( f,0,data.data() );
			}
			else{
				glInterleavedArrays( f,0,BUFFER_OFFSET(0) );
			}
		}
		
		 void bindArray () const { glBindBuffer(GL_ARRAY_BUFFER,name);	}
		 void bindBuffer() const { glBindBuffer(GL_ARRAY_BUFFER,name);	}
		 void bind		() const { glBindBuffer(GL_ARRAY_BUFFER,name);	}
		 void unbind	() const { glBindBuffer(GL_ARRAY_BUFFER,0);		}
		 void assignData () const{ std::cout << "\n" << "Vertex Arrays do not have an assignData function"; return; }
		
		 bool acceptableCoordinates (int x, int y = 1, int z = 1) const {
			return ( x >= 0 && x < sizeX && y >= 0 && y < sizeY && z >= 0 && z < sizeZ );
		}
		 void draw ( GLenum primtype, int start = 0, int length = -1 ) const {
			if ( length == -1 ) { length = size(); }
			glDrawArrays( primtype, start, length );
		}
		
		 C& acc (int x, int y = 0, int z = 0){
			if ( mappedData != NULL ){
				return mappedData[x * sizeY * sizeZ + y * sizeZ + z];
			}
			return data[x][y][z];
		}
		C& accNM(int x, int y = 0, int z = 0){
			return data[x][y][z];
		}
		C& operator () (int x, int y = 0, int z = 0){
			if ( mappedData != NULL ){
				return mappedData[x * sizeY * sizeZ + y * sizeZ + z];
			}
			return data[x][y][z];
		}
};

}
#endif